using System;
using System.IO;
using System.Collections.Generic;
using System.Diagnostics;

using Prism.Core.Audio.DirectSound.WaveFormats;
using Prism.Core.Audio.DirectSound.Compression;

namespace Prism.Core.Audio.DirectSound
{
    /// <summary>
    /// Interface for MP3 frame by frame decoder
    /// </summary>
    public interface IMp3FrameDecompressor : IDisposable
    {
        /// <summary>
        /// Decompress a single MP3 frame
        /// </summary>
        /// <param name="frame">Frame to decompress</param>
        /// <param name="dest">Output buffer</param>
        /// <param name="destOffset">Offset within output buffer</param>
        /// <returns>Bytes written to output buffer</returns>
        int DecompressFrame(Mp3Frame frame, byte[] dest, int destOffset);

        /// <summary>
        /// PCM format that we are converting into
        /// </summary>
        WaveFormat OutputFormat { get; }
    }

    class AcmMp3FrameDecompressor : IDisposable, IMp3FrameDecompressor
    {
        private AcmStream conversionStream;
        private WaveFormat pcmFormat;

        public AcmMp3FrameDecompressor(WaveFormat sourceFormat)
        {
            this.pcmFormat = AcmStream.SuggestPcmFormat(sourceFormat);
            conversionStream = new AcmStream(sourceFormat, pcmFormat);
        }

        public WaveFormat OutputFormat { get { return pcmFormat; } }

        public int DecompressFrame(Mp3Frame frame, byte[] dest, int destOffset)
        {
            Array.Copy(frame.RawData, conversionStream.SourceBuffer, frame.FrameLength);
            int sourceBytesConverted = 0;
            int converted = conversionStream.Convert(frame.FrameLength, out sourceBytesConverted);
            if (sourceBytesConverted != frame.FrameLength)
            {
                throw new InvalidOperationException("Couldn't convert the whole MP3 frame");
            }
            Array.Copy(conversionStream.DestBuffer, 0, dest, destOffset, converted);
            return converted;
        }

        public void Dispose()
        {
            if (this.conversionStream != null)
            {
                this.conversionStream.Dispose();
                this.conversionStream = null;
            }
        }
    }

    class Mp3Index
    {
        public long FilePosition { get; set; }
        public int SamplePosition { get; set; }
        public int SampleCount { get; set; }
        public int ByteCount { get; set; }
    }

    /// <summary>
    /// Class for reading from MP3 files
    /// </summary>
    public class Mp3FileReader : WaveStream
    {
        private WaveFormat waveFormat;
        private Stream mp3Stream;
        private long mp3DataLength;
        private long dataStartPosition;
        private int frameLengthInBytes;

        /// <summary>
        /// The MP3 wave format (n.b. NOT the output format of this stream - see the WaveFormat property)
        /// </summary>
        public Mp3WaveFormat Mp3WaveFormat { get; private set; }

        private Id3v2Tag id3v2Tag;
        private XingHeader xingHeader;
        private byte[] id3v1Tag;
        private bool ownInputStream;

        private List<Mp3Index> tableOfContents;
        private int tocIndex;

        private int sampleRate;
        private int totalSamples;
        private int bytesPerSample;

        private IMp3FrameDecompressor decompressor;

        private byte[] decompressBuffer;
        private int decompressBufferOffset;
        private int decompressLeftovers;

        private object repositionLock = new object();

        /// <summary>Supports opening a MP3 file</summary>
        public Mp3FileReader(string mp3FileName)
            : this(File.OpenRead(mp3FileName))
        {
            ownInputStream = true;
        }

        /// <summary>
        /// Opens MP3 from a stream rather than a file
        /// Will not dispose of this stream itself
        /// </summary>
        /// <param name="inputStream"></param>
        public Mp3FileReader(Stream inputStream)
        {
            // Calculated as a double to minimize rounding errors
            double bitRate;

            mp3Stream = inputStream;
            id3v2Tag = Id3v2Tag.ReadTag(mp3Stream);

            dataStartPosition = mp3Stream.Position;
            Mp3Frame mp3Frame = new Mp3Frame(mp3Stream);
            sampleRate = mp3Frame.SampleRate;
            frameLengthInBytes = mp3Frame.FrameLength;
            bitRate = mp3Frame.BitRate;
            xingHeader = XingHeader.LoadXingHeader(mp3Frame);
            // If the header exists, we can skip over it when decoding the rest of the file
            if (xingHeader != null) dataStartPosition = mp3Stream.Position;

            this.mp3DataLength = mp3Stream.Length - dataStartPosition;

            // try for an ID3v1 tag as well
            mp3Stream.Position = mp3Stream.Length - 128;
            byte[] tag = new byte[128];
            mp3Stream.Read(tag, 0, 3);
            if (tag[0] == 'T' && tag[1] == 'A' && tag[2] == 'G')
            {
                id3v1Tag = tag;
                this.mp3DataLength -= 128;
            }

            mp3Stream.Position = dataStartPosition;

            CreateTableOfContents();
            this.tocIndex = 0;

            // [Bit rate in Kilobits/sec] = [Length in kbits] / [time in seconds] 
            //                            = [Length in bits ] / [time in milliseconds]

            // Note: in audio, 1 kilobit = 1000 bits.
            bitRate = (mp3DataLength * 8.0 / TotalSeconds());

            mp3Stream.Position = dataStartPosition;

            this.Mp3WaveFormat = new Mp3WaveFormat(sampleRate, mp3Frame.ChannelMode == ChannelMode.Mono ? 1 : 2, frameLengthInBytes, (int)bitRate);
            decompressor = new AcmMp3FrameDecompressor(this.Mp3WaveFormat); // new DmoMp3FrameDecompressor(this.Mp3WaveFormat); 
            this.waveFormat = decompressor.OutputFormat;
            this.bytesPerSample = (decompressor.OutputFormat.BitsPerSample) / 8 * decompressor.OutputFormat.Channels;
            // no MP3 frames have more than 1152 samples in them
            // some MP3s I seem to get double
            this.decompressBuffer = new byte[1152 * bytesPerSample * 2];
        }

        private void CreateTableOfContents()
        {
            try
            {
                // Just a guess at how many entries we'll need so the internal array need not resize very much
                // 400 bytes per frame is probably a good enough approximation.
                tableOfContents = new List<Mp3Index>((int)(mp3DataLength / 400));
                Mp3Frame frame = null;
                this.totalSamples = 0;
                do
                {
                    Mp3Index index = new Mp3Index();
                    index.FilePosition = mp3Stream.Position;
                    index.SamplePosition = totalSamples;
                    frame = ReadNextFrame(false);
                    if (frame != null)
                    {
                        totalSamples += frame.SampleCount;
                        index.SampleCount = frame.SampleCount;
                        index.ByteCount = (int)(mp3Stream.Position - index.FilePosition);
                        tableOfContents.Add(index);
                    }
                } while (frame != null);
            }
            catch (EndOfStreamException)
            {
                // not necessarily a problem
            }
        }

        /// <summary>
        /// Gets the total length of this file in milliseconds.
        /// </summary>
        private double TotalSeconds()
        {
            return (double)this.totalSamples / sampleRate;
        }

        /// <summary>
        /// ID3v2 tag if present
        /// </summary>
        public Id3v2Tag Id3v2Tag
        {
            get { return id3v2Tag; }
        }

        /// <summary>
        /// ID3v1 tag if present
        /// </summary>
        public byte[] Id3v1Tag
        {
            get { return id3v1Tag; }
        }

        /// <summary>
        /// Reads the next mp3 frame
        /// </summary>
        /// <returns>Next mp3 frame, or null if EOF</returns>
        public Mp3Frame ReadNextFrame()
        {
            return ReadNextFrame(true);
        }

        /// <summary>
        /// Reads the next mp3 frame
        /// </summary>
        /// <returns>Next mp3 frame, or null if EOF</returns>
        public Mp3Frame ReadNextFrame(bool readData)
        {
            if (mp3Stream.Position + frameLengthInBytes <= mp3DataLength + dataStartPosition)
            {
                var frame = new Mp3Frame(mp3Stream, readData);
                tocIndex++;
                return frame;
            }
            return null;
        }

        /// <summary>
        /// This is the length in bytes of data available to be read out from the Read method
        /// (i.e. the decompressed MP3 length)
        /// n.b. this may return 0 for files whose length is unknown
        /// </summary>
        public override long Length
        {
            get
            {
                return this.totalSamples * this.bytesPerSample; // assume converting to 16 bit (n.b. may have to check if this includes) //length;
            }
        }

        /// <summary>
        /// <see cref="WaveStream.WaveFormat"/>
        /// </summary>
        public override WaveFormat WaveFormat
        {
            get { return waveFormat; }
        }

        /// <summary>
        /// <see cref="Stream.Position"/>
        /// </summary>
        public override long Position
        {
            get
            {
                if (tocIndex >= tableOfContents.Count)
                {
                    return this.Length;
                }
                else
                {
                    return (tableOfContents[tocIndex].SamplePosition * this.bytesPerSample) + decompressBufferOffset;
                }
            }
            set
            {
                lock (repositionLock)
                {
                    value = Math.Max(Math.Min(value, Length), 0);
                    var samplePosition = value / this.bytesPerSample;
                    Mp3Index mp3Index = null;
                    for (int index = 0; index < tableOfContents.Count; index++)
                    {
                        if (tableOfContents[index].SamplePosition >= samplePosition)
                        {
                            mp3Index = tableOfContents[index];
                            tocIndex = index;
                            break;
                        }
                    }
                    if (mp3Index != null)
                    {
                        // perform the reposition
                        mp3Stream.Position = mp3Index.FilePosition;
                    }
                    else
                    {
                        // we are repositioning to the end of the data
                        mp3Stream.Position = mp3DataLength + dataStartPosition;
                    }
                    decompressBufferOffset = 0;
                    decompressLeftovers = 0;
                }
            }
        }

        /// <summary>
        /// Reads decompressed PCM data from our MP3 file.
        /// </summary>
        public override int Read(byte[] sampleBuffer, int offset, int numBytes)
        {
            int bytesRead = 0;
            lock (repositionLock)
            {
                if (decompressLeftovers != 0)
                {
                    int toCopy = Math.Min(decompressLeftovers, numBytes);
                    Array.Copy(decompressBuffer, decompressBufferOffset, sampleBuffer, offset, toCopy);
                    decompressLeftovers -= toCopy;
                    if (decompressLeftovers == 0)
                    {
                        decompressBufferOffset = 0;
                    }
                    else
                    {
                        decompressBufferOffset += toCopy;
                    }
                    bytesRead += toCopy;
                    offset += toCopy;
                }

                while (bytesRead < numBytes)
                {
                    Mp3Frame frame = ReadNextFrame();
                    if (frame != null)
                    {
                        int decompressed = decompressor.DecompressFrame(frame, decompressBuffer, 0);
                        int toCopy = Math.Min(decompressed, numBytes - bytesRead);
                        Array.Copy(decompressBuffer, 0, sampleBuffer, offset, toCopy);
                        if (toCopy < decompressed)
                        {
                            decompressBufferOffset = toCopy;
                            decompressLeftovers = decompressed - toCopy;
                        }
                        else
                        {
                            // no lefovers
                            decompressBufferOffset = 0;
                            decompressLeftovers = 0;
                        }
                        offset += toCopy;
                        bytesRead += toCopy;
                    }
                    else
                    {
                        break;
                    }
                }
            }
            Debug.Assert(bytesRead <= numBytes, "MP3 File Reader read too much");
            return bytesRead;
        }

        /// <summary>
        /// Xing header if present
        /// </summary>
        public XingHeader XingHeader
        {
            get { return xingHeader; }
        }

        /// <summary>
        /// Disposes this WaveStream
        /// </summary>
        protected override void Dispose(bool disposing)
        {
            if (disposing)
            {
                if (mp3Stream != null)
                {
                    if (ownInputStream)
                    {
                        mp3Stream.Dispose();
                    }
                    mp3Stream = null;
                }
                if (decompressor != null)
                {
                    decompressor.Dispose();
                    decompressor = null;
                }
            }
            base.Dispose(disposing);
        }
    }

    /// <summary>
    /// An ID3v2 Tag
    /// </summary>
    public class Id3v2Tag
    {
        private long tagStartPosition;
        private long tagEndPosition;
        private byte[] rawData;

        /// <summary>
        /// Reads an ID3v2 tag from a stream
        /// </summary>
        public static Id3v2Tag ReadTag(Stream input)
        {
            return new Id3v2Tag(input);
        }

        private Id3v2Tag(Stream input)
        {
            tagStartPosition = input.Position;
            BinaryReader reader = new BinaryReader(input);
            byte[] headerBytes = reader.ReadBytes(10);
            if ((headerBytes[0] == (byte)'I') &&
                (headerBytes[1] == (byte)'D') &&
                (headerBytes[2] == '3'))
            {

                // http://www.id3.org/develop.html
                // OK found an ID3 tag
                // bytes 3 & 4 are ID3v2 version

                if ((headerBytes[5] & 0x40) == 0x40)
                {
                    // extended header present
                    byte[] extendedHeader = reader.ReadBytes(4);
                    int extendedHeaderLength = extendedHeader[0] * (1 << 21);
                    extendedHeaderLength += extendedHeader[1] * (1 << 14);
                    extendedHeaderLength += extendedHeader[2] * (1 << 7);
                    extendedHeaderLength += extendedHeader[3];
                }

                // synchsafe
                int dataLength = headerBytes[6] * (1 << 21);
                dataLength += headerBytes[7] * (1 << 14);
                dataLength += headerBytes[8] * (1 << 7);
                dataLength += headerBytes[9];
                byte[] tagData = reader.ReadBytes(dataLength);

                if ((headerBytes[5] & 0x10) == 0x10)
                {
                    // footer present
                    byte[] footer = reader.ReadBytes(10);
                }
            }
            else
            {
                input.Position -= 10;
                throw new FormatException("Not an ID3v2 tag");
            }
            tagEndPosition = input.Position;
            input.Position = tagStartPosition;
            rawData = reader.ReadBytes((int)(tagEndPosition - tagStartPosition));

        }

        /// <summary>
        /// Raw data from this tag
        /// </summary>
        public byte[] RawData
        {
            get
            {
                return rawData;
            }
        }
    }

    /// <summary>
    /// Represents a Xing VBR header
    /// </summary>
    public class XingHeader
    {
        [Flags]
        enum XingHeaderOptions
        {
            Frames = 1,
            Bytes = 2,
            Toc = 4,
            VbrScale = 8
        }

        private static int[] sr_table = { 44100, 48000, 32000, 99999 };
        private int vbrScale = -1;
        private int startOffset;
        private int endOffset;

        private int tocOffset = -1;
        private int framesOffset = -1;
        private int bytesOffset = -1;
        private Mp3Frame frame;

        private static int ReadBigEndian(byte[] buffer, int offset)
        {
            int x;
            // big endian extract
            x = buffer[offset + 0];
            x <<= 8;
            x |= buffer[offset + 1];
            x <<= 8;
            x |= buffer[offset + 2];
            x <<= 8;
            x |= buffer[offset + 3];

            return x;
        }

        private void WriteBigEndian(byte[] buffer, int offset, int value)
        {
            byte[] littleEndian = BitConverter.GetBytes(value);
            for (int n = 0; n < 4; n++)
            {
                buffer[offset + 4 - n] = littleEndian[n];
            }
        }

        /// <summary>
        /// Load Xing Header
        /// </summary>
        /// <param name="frame">Frame</param>
        /// <returns>Xing Header</returns>
        public static XingHeader LoadXingHeader(Mp3Frame frame)
        {
            XingHeader xingHeader = new XingHeader();
            xingHeader.frame = frame;
            int offset = 0;

            if (frame.MpegVersion == MpegVersion.Version1)
            {
                if (frame.ChannelMode != ChannelMode.Mono)
                    offset = 32 + 4;
                else
                    offset = 17 + 4;
            }
            else if (frame.MpegVersion == MpegVersion.Version2)
            {
                if (frame.ChannelMode != ChannelMode.Mono)
                    offset = 17 + 4;
                else
                    offset = 9 + 4;
            }
            else
            {
                return null;
                // throw new FormatException("Unsupported MPEG Version");
            }

            if ((frame.RawData[offset + 0] == 'X') &&
                (frame.RawData[offset + 1] == 'i') &&
                (frame.RawData[offset + 2] == 'n') &&
                (frame.RawData[offset + 3] == 'g'))
            {
                xingHeader.startOffset = offset;
                offset += 4;
            }
            else
            {
                return null;
            }

            XingHeaderOptions flags = (XingHeaderOptions)ReadBigEndian(frame.RawData, offset);
            offset += 4;

            if ((flags & XingHeaderOptions.Frames) != 0)
            {
                xingHeader.framesOffset = offset;
                offset += 4;
            }
            if ((flags & XingHeaderOptions.Bytes) != 0)
            {
                xingHeader.bytesOffset = offset;
                offset += 4;
            }
            if ((flags & XingHeaderOptions.Toc) != 0)
            {
                xingHeader.tocOffset = offset;
                offset += 100;
            }
            if ((flags & XingHeaderOptions.VbrScale) != 0)
            {
                xingHeader.vbrScale = ReadBigEndian(frame.RawData, offset);
                offset += 4;
            }
            xingHeader.endOffset = offset;
            return xingHeader;
        }

        /// <summary>
        /// Sees if a frame contains a Xing header
        /// </summary>
        private XingHeader()
        {
        }

        /// <summary>
        /// Number of frames
        /// </summary>
        public int Frames
        {
            get
            {
                if (framesOffset == -1)
                    return -1;
                return ReadBigEndian(frame.RawData, framesOffset);
            }
            set
            {
                if (framesOffset == -1)
                    throw new InvalidOperationException("Frames flag is not set");
                WriteBigEndian(frame.RawData, framesOffset, value);
            }
        }

        /// <summary>
        /// Number of bytes
        /// </summary>
        public int Bytes
        {
            get
            {
                if (bytesOffset == -1)
                    return -1;
                return ReadBigEndian(frame.RawData, bytesOffset);
            }
            set
            {
                if (framesOffset == -1)
                    throw new InvalidOperationException("Bytes flag is not set");
                WriteBigEndian(frame.RawData, bytesOffset, value);
            }
        }

        /// <summary>
        /// VBR Scale property
        /// </summary>
        public int VbrScale
        {
            get { return vbrScale; }
        }

        /// <summary>
        /// The MP3 frame
        /// </summary>
        public Mp3Frame Mp3Frame
        {
            get { return frame; }
        }

    }

    /// <summary>
    /// MPEG Layer flags
    /// </summary>
    public enum MpegLayer
    {
        /// <summary>
        /// Reserved
        /// </summary>
        Reserved,
        /// <summary>
        /// Layer 3
        /// </summary>
        Layer3,
        /// <summary>
        /// Layer 2
        /// </summary>
        Layer2,
        /// <summary>
        /// Layer 1
        /// </summary>
        Layer1
    }

    /// <summary>
    /// MPEG Version Flags
    /// </summary>
    public enum MpegVersion
    {
        /// <summary>
        /// Version 2.5
        /// </summary>
        Version25,
        /// <summary>
        /// Reserved
        /// </summary>
        Reserved,
        /// <summary>
        /// Version 2
        /// </summary>
        Version2,
        /// <summary>
        /// Version 1
        /// </summary>
        Version1
    }

    /// <summary>
    /// Channel Mode
    /// </summary>
    public enum ChannelMode
    {
        /// <summary>
        /// Stereo
        /// </summary>
        Stereo,
        /// <summary>
        /// Joint Stereo
        /// </summary>
        JointStereo,
        /// <summary>
        /// Dual Channel
        /// </summary>
        DualChannel,
        /// <summary>
        /// Mono
        /// </summary>
        Mono
    }

    /// <summary>
    /// Represents an MP3 Frame
    /// </summary>
    public class Mp3Frame
    {
        private static readonly int[, ,] bitRates = new int[,,] {
            {
                // MPEG Version 1
                { 0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448 }, // Layer 1
                { 0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384 }, // Layer 2
                { 0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320 }, // Layer 3
            },
            {
                // MPEG Version 2 & 2.5
                { 0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256 }, // Layer 1
                { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, // Layer 2 
                { 0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160 }, // Layer 3 (same as layer 2)
            }
        };

        private static readonly int[,] samplesPerFrame = new int[,] {
            {   // MPEG Version 1
                384,    // Layer1
                1152,   // Layer2
                1152    // Layer3
            },
            {   // MPEG Version 2 & 2.5
                384,    // Layer1
                1152,   // Layer2
                576     // Layer3
            }
        };

        private static readonly int[] sampleRatesVersion1 = new int[] { 44100, 48000, 32000 };
        private static readonly int[] sampleRatesVersion2 = new int[] { 22050, 24000, 16000 };
        private static readonly int[] sampleRatesVersion25 = new int[] { 11025, 12000, 8000 };

        private int bitRate;
        private bool crcPresent;
        //private short crc;
        private int sampleRate;
        private int frameLengthInBytes;
        private byte[] rawData;
        private MpegVersion mpegVersion;
        private MpegLayer layer;
        private ChannelMode channelMode;
        private const int MaxFrameLength = 16 * 1024;
        private int samplesInFrame; // number of samples in this frame

        /// <summary>Reads an MP3Frame from a stream</summary>
        /// <remarks>http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm has some good info
        /// also see http://www.codeproject.com/KB/audio-video/mpegaudioinfo.aspx
        /// </remarks>
        public Mp3Frame(Stream input)
            : this(input, true)
        { }


        /// <summary>Reads an MP3Frame from a stream</summary>
        /// <remarks>http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm 
        /// has some good info</remarks>
        /// <exception cref="EndOfStreamException">Thrown when we reach the end of the stream without reading a valid frame</exception>
        public Mp3Frame(Stream input, bool readData)
        {
            lock (this)
            {
                BinaryReader reader = new BinaryReader(input);
                // try for a header
                long headerStartPosition = input.Position;
                byte[] headerBytes = reader.ReadBytes(4);
                if (headerBytes.Length < 4)
                {
                    throw new EndOfStreamException();
                }

                // Added -jam to play wrapped mp3 files via RIFF
                headerBytes = CheckForRiff(input, reader, headerBytes);

                while (!IsValidHeader(headerBytes))
                {
                    //headerStartPosition++;
                    byte extra = reader.ReadByte(); // n.b can throw an end of stream exception
                    // shift down by one and try again
                    headerBytes[0] = headerBytes[1];
                    headerBytes[1] = headerBytes[2];
                    headerBytes[2] = headerBytes[3];
                    headerBytes[3] = extra;
                }
                /* no longer read the CRC since we include this in framelengthbytes
                if (this.crcPresent)
                    this.crc = reader.ReadInt16();*/

                this.rawData = new byte[this.frameLengthInBytes];
                Array.Copy(headerBytes, this.rawData, 4);
                int bytesRead = reader.Read(rawData, 4, this.frameLengthInBytes - 4);
                if (bytesRead < this.frameLengthInBytes - 4)
                {
                    throw new EndOfStreamException();
                }
            }
        }

        /// <summary>checks if the four bytes represent a valid header,
        /// if they are, will parse the values into local properties
        /// </summary>
        private bool IsValidHeader(byte[] headerBytes)
        {
            if ((headerBytes[0] == 0xFF) && ((headerBytes[1] & 0xE0) == 0xE0))
            {
                // TODO: could do with a bitstream class here
                mpegVersion = (MpegVersion)((headerBytes[1] & 0x18) >> 3);
                if (mpegVersion == MpegVersion.Reserved)
                {
                    //throw new FormatException("Unsupported MPEG Version");
                    return false;
                }

                layer = (MpegLayer)((headerBytes[1] & 0x06) >> 1);

                if (layer == MpegLayer.Reserved)
                {
                    return false;
                }
                int layerIndex = this.layer == MpegLayer.Layer1 ? 0 : this.layer == MpegLayer.Layer2 ? 1 : 2;
                crcPresent = (headerBytes[1] & 0x01) == 0x00;
                int bitRateIndex = (headerBytes[2] & 0xF0) >> 4;
                if (bitRateIndex == 15)
                {
                    // invalid index
                    return false;
                }
                int versionIndex = this.mpegVersion == MpegVersion.Version1 ? 0 : 1;
                this.bitRate = bitRates[versionIndex, layerIndex, bitRateIndex] * 1000;
                if (bitRate == 0)
                {
                    return false;
                }
                int sampleFrequencyIndex = (headerBytes[2] & 0x0C) >> 2;
                if (sampleFrequencyIndex == 3)
                {
                    return false;
                }
                if (mpegVersion == MpegVersion.Version1)
                    sampleRate = sampleRatesVersion1[sampleFrequencyIndex];
                else if (mpegVersion == MpegVersion.Version2)
                    sampleRate = sampleRatesVersion2[sampleFrequencyIndex];
                else // mpegVersion == MpegVersion.Version25
                    sampleRate = sampleRatesVersion25[sampleFrequencyIndex];

                bool padding = (headerBytes[2] & 0x02) == 0x02;
                bool privateBit = (headerBytes[2] & 0x01) == 0x01;
                channelMode = (ChannelMode)((headerBytes[3] & 0xC0) >> 6);
                int channelExtension = (headerBytes[3] & 0x30) >> 4;
                bool copyright = (headerBytes[3] & 0x08) == 0x08;
                bool original = (headerBytes[3] & 0x04) == 0x04;
                int emphasis = (headerBytes[3] & 0x03);

                int nPadding = padding ? 1 : 0;

                this.samplesInFrame = samplesPerFrame[versionIndex, layerIndex];
                int coefficient = this.samplesInFrame / 8;
                if (this.layer == MpegLayer.Layer1)
                {
                    this.frameLengthInBytes = (coefficient * bitRate / sampleRate + nPadding) * 4;
                }
                else
                {
                    frameLengthInBytes = (coefficient * bitRate) / sampleRate + nPadding;
                }

                if (this.frameLengthInBytes > MaxFrameLength)
                {
                    return false;
                }
                return true;
            }
            return false;
        }

        private static byte[] CheckForRiff(Stream input, BinaryReader reader, byte[] headerBytes)
        {
            if ((headerBytes[0] == 'R') &&
                (headerBytes[1] == 'I') &&
                (headerBytes[2] == 'F') &&
                (headerBytes[3] == 'F'))
            {
                // Backup 4 bytes
                input.Position -= 4;

                // Now start parsing
                WaveFormat format;
                long dataChunkPosition;
                int dataChunkLength;

                WaveFileReader.ReadWaveHeader(input, out format, out dataChunkPosition, out dataChunkLength, null);

                // Now read the actual mp3 header
                input.Position = dataChunkPosition;
                headerBytes = reader.ReadBytes(4);
            }
            return headerBytes;
        }

        /// <summary>
        /// Sample rate of this frame
        /// </summary>
        public int SampleRate
        {
            get
            {
                return sampleRate;
            }
        }

        /// <summary>
        /// Frame length in bytes
        /// </summary>
        public int FrameLength
        {
            get
            {
                return frameLengthInBytes;
            }
        }

        /// <summary>
        /// Bit Rate
        /// </summary>
        public int BitRate
        {
            get { return bitRate; }
        }

        /// <summary>
        /// Raw frame data
        /// </summary>
        public byte[] RawData
        {
            get { return rawData; }
        }

        /// <summary>
        /// MPEG Version
        /// </summary>
        public MpegVersion MpegVersion
        {
            get { return mpegVersion; }
        }

        /// <summary>
        /// MPEG Layer
        /// </summary>
        public MpegLayer MpegLayer
        {
            get { return layer; }
        }

        /// <summary>
        /// Channel Mode
        /// </summary>
        public ChannelMode ChannelMode
        {
            get { return channelMode; }
        }

        /// <summary>
        /// The number of samples in this frame
        /// </summary>
        public int SampleCount
        {
            get { return samplesInFrame; }
        }
    }
}
